#include "AudioProcessor.h"
#include "core/gamestream/GameStreamManager.h"
#include "core/gamestream/XComponentBackend.h"
#include "core/utils/AVUtils.h"
#include "core/utils/AudioStreamUtils.h"
#include <ohaudio/native_audiostreambuilder.h>
#include <cstdint>

#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN  0x0
#define LOG_TAG     "AudioRendererCallbacks"
#include <hilog/log.h>


using LimelightAudioCallbacks = AudioProcessor::LimelightAudioCallbacks;

int LimelightAudioCallbacks::init(int audioConfiguration, const POPUS_MULTISTREAM_CONFIGURATION opusConfig, void *context, int arFlags) {
    OH_LOG_INFO(LOG_APP,
        "Limelight setting-up audio renderer with audioConfig %{public}d, sample rate %{public}d, channel %{public}d, "
        "streams %{public}d, coupledStreams %{public}d, samplesPerFrame %{public}d",
        audioConfiguration, opusConfig->sampleRate, opusConfig->channelCount,
        opusConfig->streams, opusConfig->coupledStreams, opusConfig->samplesPerFrame
    );
    INIT_AUDIO_PROCESSOR_SELF_OR_ELSE(return -1);
    self.limelightParams = {
        .audioConfiguration = audioConfiguration,
        .opusConfig = *opusConfig,
        .context = context,
        .arFlags = arFlags,
    };
    return 0;
}

void LimelightAudioCallbacks::start() {
    OH_LOG_INFO(LOG_APP, "Limelight instructed audio renderer start");
    INIT_AUDIO_PROCESSOR_SELF_OR_ELSE(return);
    /* setup decoder */
    {
        int opusErr = OPUS_OK;
        self.decoder = opus_multistream_decoder_create(
            self.limelightParams.opusConfig.sampleRate,
            self.limelightParams.opusConfig.channelCount,
            self.limelightParams.opusConfig.streams,
            self.limelightParams.opusConfig.coupledStreams,
            self.limelightParams.opusConfig.mapping,
            &opusErr
        );
        if (opusErr != OPUS_OK) {
            OH_LOG_ERROR(LOG_APP, "Failed to create audio decoder %{public}d", opusErr);
            self.decoder = nullptr;
            return;
        }
    }
    const auto cleanup1 = [&] {
        auto *dec = self.decoder;
        self.decoder = nullptr;
        opus_multistream_decoder_destroy(dec);
    };
    /* setup renderer */
    const auto builder = AVUtils::makeAudioStreamBuilder(AUDIOSTREAM_TYPE_RENDERER);
    AS_CALL_OR_ELSE(OH_AudioStreamBuilder_SetSamplingRate(builder.get(), self.limelightParams.opusConfig.sampleRate), cleanup1(); return);
    AS_CALL_OR_ELSE(OH_AudioStreamBuilder_SetChannelCount(builder.get(), self.limelightParams.opusConfig.channelCount), cleanup1(); return);
    AS_CALL_OR_ELSE(OH_AudioStreamBuilder_SetSampleFormat(builder.get(), AUDIOSTREAM_SAMPLE_S16LE), cleanup1(); return);
    AS_CALL_OR_ELSE(OH_AudioStreamBuilder_SetEncodingType(builder.get(), AUDIOSTREAM_ENCODING_TYPE_RAW), cleanup1(); return);
    AS_CALL_OR_ELSE(OH_AudioStreamBuilder_SetRendererInfo(builder.get(), AUDIOSTREAM_USAGE_GAME), cleanup1(); return);
    AS_CALL_OR_ELSE(OH_AudioStreamBuilder_SetChannelLayout(builder.get(), CH_LAYOUT_STEREO), cleanup1(); return);
    AS_CALL_OR_ELSE(OH_AudioStreamBuilder_SetLatencyMode(builder.get(), AUDIOSTREAM_LATENCY_MODE_FAST), cleanup1(); return);
    AS_CALL_OR_ELSE(OH_AudioStreamBuilder_SetRendererCallback(builder.get(), AudioRendererCallbacks::callbacks, &self), cleanup1(); return);
    AS_CALL_OR_ELSE(OH_AudioStreamBuilder_GenerateRenderer(builder.get(), &self.renderer), cleanup1(); return);
    const auto cleanup2 = [&] {
        auto *rnd = self.renderer;
        self.renderer = nullptr;
        AS_CALL_OR_ELSE(OH_AudioRenderer_Release(rnd),);
        cleanup1();
    };
    AS_CALL_OR_ELSE(OH_AudioRenderer_Start(self.renderer), cleanup2(); return);
}

void LimelightAudioCallbacks::stop() {
    OH_LOG_INFO(LOG_APP, "Limelight instructed audio renderer stop");
    INIT_AUDIO_PROCESSOR_SELF_OR_ELSE(return);
    self.xcomp.stopFlag = true;
}

void LimelightAudioCallbacks::cleanup() {
    OH_LOG_INFO(LOG_APP, "Limelight instructed audio renderer cleanup");
    INIT_AUDIO_PROCESSOR_SELF_OR_ELSE(return);
    self.xcomp.stopFlag = true;
}

void LimelightAudioCallbacks::decodeAndPlaySample(char *sampleData, int sampleLength) {
    if constexpr (DEBUG) {
        OH_LOG_DEBUG(LOG_APP, "Limelight submitting an audio sample, length %{public}d", sampleLength);
    }
    INIT_AUDIO_PROCESSOR_SELF_OR_ELSE(return);
    if (self.xcomp.stopFlag) {
        OH_LOG_INFO(LOG_APP, "Calling decodeAndPlaySample on already stopped XComponentBackend");
        return;
    }
    PcmBufferSpan buffer;
    buffer.buffer = std::make_unique<opus_int16[]>(self.limelightParams.opusConfig.channelCount * self.limelightParams.opusConfig.samplesPerFrame);
    const ssize_t samplesDecoded = opus_multistream_decode(
        self.decoder,
        reinterpret_cast<unsigned char*>(sampleData),
        sampleLength,
        buffer.buffer.get(),
        self.limelightParams.opusConfig.samplesPerFrame,
        0
    );
    if (samplesDecoded <= 0) {
        OH_LOG_WARN(LOG_APP, "No audio samples were decoded %{public}d", samplesDecoded);
        return;
    }
    buffer.lengthInBytes = self.limelightParams.opusConfig.channelCount * samplesDecoded * (sizeof(PcmBufferSpan::sample_t) / sizeof(uint8_t));
    {
        std::unique_lock l(self.renderMtx);
        self.renderQueue.push_back(std::move(buffer));
    }
    self.renderReadyCond.notify_one();
    if constexpr (DEBUG) {
        OH_LOG_DEBUG(LOG_APP, "Limelight submitted an audio sample, sampleLength %{public}d, samplesDecoded %{public}d, "
            "channels %{public}d, samplesPerFrame %{public}d",
            sampleLength, samplesDecoded, self.limelightParams.opusConfig.channelCount, self.limelightParams.opusConfig.samplesPerFrame);
    }
}
